#include <pthread.h>
#include <errno.h>
#include <stdio.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <stdio.h>

#include "psd_conn.h"
#include "psd_protocols.h"
#include "psd_conn_udp.h"
#include "psd_logger.h"
#include "psd_tpool.h"
#include "psd_event.h"
#include "psd_config.h"
#include "psd_modules.h"


static psdConn_udpData *psd_conn_udpdata_alloc(int sd);
static int psd_conn_udp_write(psdConn *sink, const void *buf, size_t len, int flags);
static int psd_conn_udp_read(psdConn *src, void *buf, size_t len, int flags);
static int psd_conn_udp_shutdown(psdConn *conn, uint8_t direction);
static int psd_conn_udp_setbufsize(psdConn *conn, uint8_t direction, int size);
static int psd_conn_udp_settimeout(psdConn *conn, uint8_t direction, int seconds);
static void psd_conn_udp_free(psdConn *conn);


psdConn *psd_conn_udp_alloc(int sd) {
	psdConn *new_conn;
	char buf[200];

	new_conn = psd_conn_alloc();
	if (!new_conn)
		goto error_exit;

	new_conn->conn_private = psd_conn_udpdata_alloc(sd);
	if (!new_conn->conn_private)
		goto error_exit2;

	new_conn->stats_private = psd_conn_stats_alloc_def();
	if (!new_conn->stats_private) {
		psd_err(0, "couldn't allocate udp stats structure");
		goto error_exit3;
	}

	new_conn->protocol = "UDP";

	sprintf(buf, "%d", sd);
	new_conn->description = strdup(buf);

	new_conn->read2 = psd_conn_udp_read;
	new_conn->write2 = psd_conn_udp_write;
	new_conn->shutdown2 = psd_conn_udp_shutdown;
	new_conn->setbufsize2 = psd_conn_udp_setbufsize;
	new_conn->settimeout2 = psd_conn_udp_settimeout;
	new_conn->get_stat2 = psd_conn_default_get_stat;
	new_conn->free_conn_private2 = psd_conn_udp_free;
	new_conn->free_stats2 = psd_conn_free_stats_def;
	new_conn->send_msg2 = psd_conn_default_send_msg;
	new_conn->get_msg2 = psd_conn_default_get_msg;
	new_conn->set_session_status2=psd_conn_default_set_session_status;
	return new_conn;

error_exit4:
	free(new_conn->stats_private);
error_exit3:
	free(new_conn->conn_private);
error_exit2:
	free(new_conn);
error_exit:
	return NULL;
}

psdConn_udpData *psd_conn_udpdata_alloc(int sd) {
        psdConn_udpData *new_udp_data;

        new_udp_data = (psdConn_udpData *) malloc(sizeof(psdConn_udpData));
        if (!new_udp_data)
                goto error_exit;

        if (pthread_mutex_init(&new_udp_data->lock, NULL) != 0)
                goto error_exit2;

        new_udp_data->sd = sd;
        new_udp_data->closed = 0;
	new_udp_data->max_dgram_size = 1024*32;

        return new_udp_data;

 error_exit2:
        free(new_udp_data);
 error_exit:
        return NULL;
}


static int psd_conn_udp_write(psdConn *sink, const void *buf, size_t len, int flags) {
	psdConn_udpData *udp_data = (psdConn_udpData *) sink->conn_private;
	int sent;
	int xfer_amt;
	int n;

	psd_info(10, "udp_write()");

	sent = 0;
	while (sent < len) {
		if (udp_data->max_dgram_size > (len - sent))
			xfer_amt = (len - sent);
		else
			xfer_amt = udp_data->max_dgram_size;
		
		n = sendto(udp_data->sd, buf+sent, xfer_amt, flags, &(udp_data->si_other), udp_data->slen);
		sent += n;
	}
		
	return sent;
}

static int psd_conn_udp_read(psdConn *src, void *buf, size_t len, int flags) {
	psdConn_udpData *udp_data = (psdConn_udpData *) src->conn_private;
	int recvd;
	int n;
	struct sockaddr si_other;
	int slen;	

	psd_info(10, "udp_read() for %d bytes", len);

	if (udp_data->closed == 1) {
		psd_err(0, "Tried to read from closed UDP socket");
		return -1;
	}

	recvd = 0;
	while (recvd < len) {
		n = recvfrom(udp_data->sd, buf+recvd, flags, udp_data->max_dgram_size, &si_other, &slen);
		psd_info(11, "recvfrom(%d) returned %d", udp_data->sd, n);
		if (n < 0) {
			perror("recvfrom: ");
		}
		recvd += n;
	}

	return recvd;
}

static int psd_conn_udp_shutdown(psdConn *conn, uint8_t direction) {
	psdConn_udpData *udp_data = (psdConn_udpData *) conn->conn_private;

	// just set a flag that says we no longer need this conn
	// probably need to send a message to the other side
	udp_data->closed = 1;
	return 0;
}

static int psd_conn_udp_setbufsize(psdConn *conn, uint8_t direction, int size) {
	psdConn_udpData *udp_data = (psdConn_udpData *) conn->conn_private;
	int new_bufsize;
	int junk;

	// here lie the setsockopt calls

	return 0;

error_exit:
	return -1;
}

static int psd_conn_udp_settimeout(psdConn *conn, uint8_t direction, int seconds) {
	psd_warn(10, "can't set timeout on udp sockets");
	return 0;
}

static void psd_conn_udp_free(psdConn *conn) {
	psdConn_udpData *udp_data = (psdConn_udpData *) conn->conn_private;

	free(udp_data);
}
